"""
滑块拼图验证码自动解决方案

这个脚本实现了一种自适应的滑块拼图验证码解决方案，可以适应不同设备和屏幕比例。
主要流程如下：

1. 拼图检测:
   - 固定拼图左侧位置在图像宽度的7.6633%处
   - 根据图像宽高比自适应调整拼图的垂直位置
   - 超宽屏：65%高度位置
   - 标准宽屏：60%高度位置
   - 接近正方形的屏幕：55%高度位置

2. 缺口检测:
   - 在右侧80%区域搜索缺口
   - 使用模板匹配方法查找与拼图相似的缺口
   - 如果模板匹配失败，使用二值化方法作为备用方案
   - 验证检测到的偏移量是否合理，太小时使用默认值

3. 移动距离计算:
   - 基础偏移量：缺口中心与拼图中心的距离
   - 根据图像宽高比动态调整额外偏移系数:
     - 超宽屏(>2.0): 0.08
     - 标准宽屏(>1.5): 0.10
     - 接近正方形的屏幕: 0.13
   - 使用加权平均优化最终移动位置

4. 屏幕适配:
   - 计算图像与浏览器的缩放比例
   - 确保偏移量在合理范围内(10%-95%)

该方案能够适应不同设备和分辨率，成功率明显高于固定参数的方法。
"""

from selenium import webdriver
from selenium.common.exceptions import WebDriverException
import shutil
import os
import yaml
import time
import re
import requests
import io
import base64
from PIL import Image
import numpy as np
import cv2
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.webdriver import ActionChains
import random
import traceback
import math

from besins_selenium_downloaders import (
    BaseDownloaderChrome,
    BaseDownloaderFirefox,
    solve_captcha_image,
)

def is_mostly_white(np_img, pct=5, thresh=240):
    """Check if more than pct% of the image is white/near-white"""
    # np_img is H×W×3 uint8
    white = np.all(np_img >= thresh, axis=2)
    white_pct = (white.sum() / white.size) * 100
    print(f"White pixel percentage: {white_pct:.2f}%")
    return white_pct > pct

def wait_for_captcha_rendering(driver, captcha_el, max_wait=20):
    """
    Wait for captcha to render with adaptive checking.
    Returns the fully rendered captcha image as numpy array.
    """
    print(f"Waiting for captcha to render (max {max_wait} seconds)...")
    
    # Initial wait to let the captcha start loading
    time.sleep(3)
    
    start_time = time.time()
    last_white_pct = 100  # Start high
    stable_count = 0
    
    while time.time() - start_time < max_wait:
        # Take screenshot
        png = captcha_el.screenshot_as_png
        img_np = np.array(Image.open(io.BytesIO(png)).convert("RGB"))
        
        # Check white percentage
        white = np.all(img_np >= 240, axis=2)
        white_pct = (white.sum() / white.size) * 100
        
        print(f"Captcha loading - white pixel %: {white_pct:.2f}% (elapsed: {time.time() - start_time:.1f}s)")
        
        # Save intermediate screenshot for debugging
        if white_pct > 5:
            Image.fromarray(img_np).save(f"captcha_loading_{white_pct:.0f}pct.png")
        
        # If white percentage is very low (<1%), likely fully loaded
        if white_pct < 1:
            print("Captcha appears fully loaded (very low white pixel percentage)")
            return img_np
            
        # If white percentage hasn't changed much, count as stable
        if abs(white_pct - last_white_pct) < 1.0:
            stable_count += 1
        else:
            stable_count = 0
            
        # If white percentage is stable for 3 checks and below 5%, consider it loaded
        if stable_count >= 3 and white_pct < 5:
            print(f"Captcha appears stable after {time.time() - start_time:.1f}s")
            return img_np
            
        last_white_pct = white_pct
        time.sleep(1)  # Check every second
    
    # If we've waited the maximum time, use whatever we have
    print(f"Maximum wait time reached ({max_wait}s). Using current captcha image.")
    png = captcha_el.screenshot_as_png
    return np.array(Image.open(io.BytesIO(png)).convert("RGB"))

def find_template_match(full_np):
    """
    Find the position of the puzzle piece using template matching.
    This approach uses edge detection and template matching for more accurate results.
    """
    print("Attempting template matching detection method...")
    
    try:
        h, w = full_np.shape[:2]
        # Save original for debugging
        Image.fromarray(full_np).save("captcha_template_original.png")
        
        # First try to locate the sliding puzzle piece
        # The piece is usually at the left side of the image
        piece_region = full_np[:, :int(w * 0.25)]
        Image.fromarray(piece_region).save("captcha_piece_region.png")
        
        # Apply edge detection to the puzzle piece and full image
        piece_canny = cv2.Canny(piece_region, 70, 200)
        full_canny = cv2.Canny(full_np, 70, 200)
        
        # Save edge detected images for debugging
        Image.fromarray(piece_canny).save("captcha_piece_canny.png")
        Image.fromarray(full_canny).save("captcha_full_canny.png")
        
        # 修复：不搜索整个图像，只搜索右侧区域(避免匹配回原位置)
        search_region = full_canny[:, int(w * 0.3):]  # 从30%宽度开始搜索，避开左侧的滑块
        
        # 使用模板匹配查找滑块缺口
        match_result = cv2.matchTemplate(search_region, piece_canny, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(match_result)
        
        print(f"Template matching confidence: {max_val:.4f}")
        
        # 如果匹配置信度太低，返回None
        if max_val < 0.2:
            print("Template matching confidence too low, falling back to other methods")
            return None
        
        # 计算在原图中的实际位置（加上偏移量）
        piece_h, piece_w = piece_canny.shape[:2]
        target_x = max_loc[0] + int(w * 0.3)  # 添加搜索区域的偏移量
        
        # 创建匹配可视化图像
        result_img = full_np.copy()
        cv2.rectangle(result_img, 
                     (target_x, max_loc[1]), 
                     (target_x + piece_w, max_loc[1] + piece_h), 
                     (0, 255, 0), 2)
        cv2.putText(
            result_img,
            f"Match: {max_val:.2f}, x={target_x}",
            (target_x, max_loc[1] - 10),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.7,
            (0, 255, 0),
            2
        )
        Image.fromarray(result_img).save("captcha_template_match.png")
        
        print(f"Template matching found target at x={target_x} with confidence {max_val:.4f}")
        return target_x
        
    except Exception as e:
        print(f"Error in template matching: {str(e)}")
        return None

def find_hole_x_cv(full_np):
    """Find the x-coordinate of the puzzle piece hole using OpenCV with enhanced biological pattern detection"""
    # Save original for debugging
    Image.fromarray(full_np).save("captcha_full.png")
    h, w = full_np.shape[:2]
    print(f"Original image size: {w}x{h}")
    
    # First try template matching approach
    template_x = find_template_match(full_np)
    if template_x is not None:
        print(f"Using template matching result: x={template_x}")
        return template_x
    
    print("Template matching failed or had low confidence, falling back to contour detection...")
    
    # Get the slider element (this will be used for both horizontal alignment and dimensions)
    gray = cv2.cvtColor(full_np, cv2.COLOR_RGB2GRAY)
    Image.fromarray(gray).save("captcha_gray.png")
    
    # ENHANCEMENT: Optimize for horizontal alignment with Gaussian and gradient
    # Create a horizontal projection profile
    h_projection = np.sum(gray < 180, axis=1)
    # Smooth using Gaussian blur
    smoothed_projection = cv2.GaussianBlur(
        h_projection.astype(np.float32).reshape(-1,1), (21,1), 0
    ).flatten()
    # Vertical gradient profile for edge emphasis
    grad = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
    grad_profile = np.sum(np.abs(grad), axis=1)
    combined_profile = smoothed_projection * 0.6 + grad_profile * 0.4
    # Find top rows
    peak_rows = np.argsort(combined_profile)[-10:]

    peak_center = np.mean(peak_rows)
    roi_top = max(0, int(peak_center - h * 0.2))
    roi_bottom = min(h, int(peak_center + h * 0.2))
    
    print(f"Horizontal alignment detection - ROI rows: {roi_top} to {roi_bottom}")
    
    # Create a mask for the ROI
    roi_mask = np.zeros_like(gray)
    roi_mask[roi_top:roi_bottom, :] = 255
    
    # Apply ROI mask to gray image
    roi_gray = cv2.bitwise_and(gray, gray, mask=roi_mask)
    Image.fromarray(roi_gray).save("roi_gray.png")
    
    # Try multiple threshold values to find the puzzle piece hole
    thresholds = [200, 180, 160]
    all_contours = []
    
    for threshold in thresholds:
        # Threshold for bright lines
        _, thr = cv2.threshold(roi_gray, threshold, 255, cv2.THRESH_BINARY)
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
        thr = cv2.morphologyEx(thr, cv2.MORPH_OPEN, kernel, iterations=1)
        
        if threshold == 200:  # Save the first threshold attempt
            Image.fromarray(thr).save("captcha_thresh.png")
        
        # Find contours
        cnts, _ = cv2.findContours(thr, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        print(f"Found {len(cnts)} contours with threshold {threshold}")
        
        # Filter better - exclude contours at the edges
        edge_margin = 3  # Pixels from edge
        valid_count = 0
        for c in cnts:
            x, y, w, h = cv2.boundingRect(c)
            # Skip contours touching the image edges
            if x <= edge_margin or y <= edge_margin or x+w >= full_np.shape[1]-edge_margin or y+h >= full_np.shape[0]-edge_margin:
                continue
            
            # Skip contours that are too small or too large
            area = cv2.contourArea(c)
            if 100 < area < 20000:
                all_contours.append((c, area, threshold))
                valid_count += 1
        
        print(f"  - Kept {valid_count} valid contours for threshold {threshold}")
    
    print(f"Total valid contours across all thresholds: {len(all_contours)}")
    
    # Color-based filtering for biological patterns
    # Convert to HSV for better color filtering
    hsv = cv2.cvtColor(full_np, cv2.COLOR_RGB2HSV)
    
    # Create masks for common colors in biological images (red/yellow for parrots/butterflies)
    color_masks = []
    
    # Red mask (for red birds/butterflies)
    lower_red1 = np.array([0, 120, 70])
    upper_red1 = np.array([10, 255, 255])
    mask_red1 = cv2.inRange(hsv, lower_red1, upper_red1)
    
    lower_red2 = np.array([170, 120, 70])  # Wrap-around red hue
    upper_red2 = np.array([180, 255, 255])
    mask_red2 = cv2.inRange(hsv, lower_red2, upper_red2)
    
    mask_red = cv2.bitwise_or(mask_red1, mask_red2)
    color_masks.append(("red", mask_red))
    
    # Yellow mask (for yellow birds/butterflies)
    lower_yellow = np.array([20, 100, 100])
    upper_yellow = np.array([30, 255, 255])
    mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
    color_masks.append(("yellow", mask_yellow))
    
    # Blue mask (for blue butterflies/fish)
    lower_blue = np.array([90, 100, 100])
    upper_blue = np.array([130, 255, 255])
    mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
    color_masks.append(("blue", mask_blue))
    
    # Green mask (for green elements)
    lower_green = np.array([40, 100, 100])
    upper_green = np.array([80, 255, 255])
    mask_green = cv2.inRange(hsv, lower_green, upper_green)
    color_masks.append(("green", mask_green))
    
    # Orange/brown mask (for insects like bees)
    lower_orange = np.array([10, 100, 100])
    upper_orange = np.array([25, 255, 255])
    mask_orange = cv2.inRange(hsv, lower_orange, upper_orange)
    color_masks.append(("orange", mask_orange))
    
    bio_contours = []
    # Process each color mask
    for color_name, color_mask in color_masks:
        # Apply ROI to color mask as well
        roi_color_mask = cv2.bitwise_and(color_mask, color_mask, mask=roi_mask)
        Image.fromarray(roi_color_mask).save(f"captcha_mask_{color_name}.png")
        
        # Find contours in color mask
        cnts_color, _ = cv2.findContours(roi_color_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        print(f"Found {len(cnts_color)} contours in {color_name} mask")
        
        # Filter contours by area range observed in the biological pattern images (2000-15000 px²)
        for c in cnts_color:
            area = cv2.contourArea(c)
            # Extract only contours with areas in the range we expect for puzzle piece holes
            if 100 < area < 20000:
                x, y, w, h = cv2.boundingRect(c)
                # Only keep contours not touching the edges
                if x > edge_margin and y > edge_margin and x+w < full_np.shape[1]-edge_margin and y+h < full_np.shape[0]-edge_margin:
                    # Calculate aspect ratio and density
                    aspect_ratio = w / float(h) if h > 0 else 0
                    perimeter = cv2.arcLength(c, True)
                    density = area / (perimeter ** 2) if perimeter > 0 else 0
                    
                    # Only keep contours with reasonable aspect ratio (not too elongated)
                    if 0.5 < aspect_ratio < 2.0:
                        print(f"  - Found valid {color_name} contour: area={area:.0f}, ratio={aspect_ratio:.2f}, density={density:.5f}")
                        bio_contours.append((c, area, color_name, density))
    
    print(f"Total valid biological pattern contours: {len(bio_contours)}")
    
    # Adaptive thresholding for detecting complex patterns
    adaptive_thresh = cv2.adaptiveThreshold(
        gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 21, 5
    )
    # Apply ROI to adaptive threshold
    roi_adaptive = cv2.bitwise_and(adaptive_thresh, adaptive_thresh, mask=roi_mask)
    Image.fromarray(roi_adaptive).save("captcha_adaptive_thresh.png")
    
    # Find contours in adaptive threshold
    cnts_adaptive, _ = cv2.findContours(roi_adaptive, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    print(f"Found {len(cnts_adaptive)} contours in adaptive threshold")
    
    # Filter adaptive threshold contours
    adaptive_contours = []
    for c in cnts_adaptive:
        area = cv2.contourArea(c)
        if 100 < area < 20000:
            x, y, w, h = cv2.boundingRect(c)
            if x > edge_margin and y > edge_margin and x+w < full_np.shape[1]-edge_margin and y+h < full_np.shape[0]-edge_margin:
                aspect_ratio = w / float(h) if h > 0 else 0
                if 0.5 < aspect_ratio < 2.0:
                    perimeter = cv2.arcLength(c, True)
                    density = area / (perimeter ** 2) if perimeter > 0 else 0
                    adaptive_contours.append((c, area, "adaptive", density))
    
    print(f"Total valid adaptive threshold contours: {len(adaptive_contours)}")
    
    # Combine results from all approaches and create a debug image showing all candidate holes
    combined_contours = all_contours + bio_contours + adaptive_contours
    
    if not combined_contours:
        print("No suitable contours found in any detection method")
        return None
    
    # Create debug image with contours from all approaches
    debug_img = full_np.copy()
    colors = {
        200: (0, 255, 0),  # Green for threshold 200
        180: (255, 0, 0),  # Red for threshold 180
        160: (0, 0, 255),  # Blue for threshold 160
        "red": (0, 0, 255),  # Red for red mask
        "yellow": (0, 255, 255),  # Yellow for yellow mask
        "blue": (255, 0, 0),  # Blue for blue mask 
        "green": (0, 255, 0),  # Green for green mask
        "orange": (0, 165, 255),  # Orange for orange mask
        "adaptive": (255, 255, 0)  # Cyan for adaptive threshold
    }
    
    # Draw all contours for debugging
    for i, (c, area, method, *extra) in enumerate(combined_contours[:10]):  # Show top 10 contours
        color = colors.get(method, (255, 255, 255))  # Default to white
        cv2.drawContours(debug_img, [c], -1, color, 2)
        x, y, w, h = cv2.boundingRect(c)
        cv2.rectangle(debug_img, (x, y), (x+w, y+h), color, 2)
        
        # Add more information for debugging
        density = extra[0] if len(extra) > 0 else 0
        label = f"{i}: {method}, area={area:.0f}, d={density:.3f}"
        cv2.putText(debug_img, label, (x, y-5), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
    
    Image.fromarray(debug_img).save("captcha_all_contours.png")
    
    # Score and rank all contours based on multiple factors:
    # 1. Area in the expected range (4000-15000)
    # 2. Higher density (more complex shape = biological pattern)
    # 3. Reasonable aspect ratio (0.7-1.3, close to square)
    # 4. Not too close to edges
    # 5. ENHANCEMENT: Give boost to contours within the ROI
    scored_contours = []
    
    for c, area, method, *extra in combined_contours:
        x, y, w, h = cv2.boundingRect(c)
        
        # Calculate base score from area (peak at ~7000-9000)
        area_score = 1.0 - abs((area - 7500) / 7500)
        area_score = max(0, min(1, area_score))
        
        # Calculate position score (higher when away from edges)
        edge_dist = min(x, y, full_np.shape[1]-x-w, full_np.shape[0]-y-h)
        pos_score = min(1.0, edge_dist / 50.0)  # Max score at 50+ pixels from edge
        
        # Calculate aspect ratio score (peak at ratio=1, square)
        aspect_ratio = w / float(h) if h > 0 else 999
        ratio_score = 1.0 - min(1.0, abs(aspect_ratio - 1.0))
        
        # Calculate density score if available
        density = extra[0] if len(extra) > 0 else 0
        density_score = min(1.0, density * 20)  # Normalize density
        
        # Calculate ROI alignment score - boost if in ROI
        roi_score = 1.0 if roi_top <= y <= roi_bottom else 0.5
        
        # Calculate total score (weighted sum)
        total_score = (
            0.25 * area_score +   # Area has 25% weight
            0.25 * pos_score +    # Position has 25% weight
            0.15 * ratio_score +  # Aspect ratio has 15% weight
            0.15 * density_score + # Density has 15% weight
            0.20 * roi_score      # ROI alignment has 20% weight
        )
        
        scored_contours.append((c, area, method, x, y, w, h, total_score))
    
    # Sort by total score (highest first)
    scored_contours.sort(key=lambda x: x[7], reverse=True)
    
    if not scored_contours:
        print("No scored contours available")
        return None
    
    # Get the highest scoring contour
    best_contour, best_area, method, x, y, w, h, score = scored_contours[0]
    print(f"Selected puzzle piece at x={x}, y={y}, width={w}, height={h}, area={best_area:.0f}, method={method}, score={score:.4f}")
    
    # Draw the selected contour for debugging
    result_img = full_np.copy()
    cv2.rectangle(result_img, (x, y), (x+w, y+h), (0, 255, 0), 2)
    cv2.putText(result_img, f"BEST: {method}, score={score:.2f}", (x, y-5), 
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
    Image.fromarray(result_img).save("debug_best_hole.png")
    
    # The target position is the left edge of the hole (the x value)
    target_x = x
    
    # Adjust target_x based on the hole dimensions (observed in image patterns)
    # For biological patterns, we want to aim for ~40% of the hole width
    adjustment = 0.4 * w
    adjusted_target = target_x + adjustment
    
    print(f"Raw target x: {target_x}, Adjusted for internal pattern: {adjusted_target:.1f}")
    
    # Verify detection by checking for the company logo presence
    # (云南省医药有限公司 text in the lower right)
    try:
        verify_company_logo(full_np)
        print("Company logo verification passed")
    except Exception as e:
        print(f"Company logo verification warning: {str(e)}")
        # Continue anyway - this is just a secondary check
    
    return adjusted_target

def verify_company_logo(full_np):
    """Verify presence of 云南省医药有限公司 text in the lower part of the image"""
    # Extract lower part of the image
    h, w = full_np.shape[:2]
    lower_section = full_np[int(h*0.85):h, :]
    
    # Convert to HSV for better text detection (white/light text)
    hsv = cv2.cvtColor(lower_section, cv2.COLOR_RGB2HSV)
    
    # Mask for white/light text
    lower_white = np.array([0, 0, 180])
    upper_white = np.array([180, 30, 255])
    mask = cv2.inRange(hsv, lower_white, upper_white)
    
    # Save for debugging
    Image.fromarray(mask).save("company_logo_detection.png")
    
    # Check if there's enough white pixels in the lower right quadrant (text)
    right_section = mask[:, int(w*0.7):w]
    white_ratio = np.count_nonzero(right_section) / right_section.size
    
    print(f"Company logo detection - white pixel ratio: {white_ratio:.4f}")
    
    if white_ratio > 0.05:  # At least 5% white pixels
        return True
    else:
        print("Warning: Company logo not clearly detected, might not be a valid captcha")
        return False

def solve_slider_captcha(driver):
    """
    Solve slider captcha using CV.
    Will retry if it fails, with complete recapture and reanalysis.
    """
    # 创建日期时间格式的结果目录
    result_dir = time.strftime("captcha_results_%Y%m%d_%H%M%S")
    try:
        if not os.path.exists(result_dir):
            os.makedirs(result_dir)
        print(f"保存结果到目录: {result_dir}")
    except Exception as e:
        print(f"无法创建结果目录: {str(e)}")
        result_dir = "."  # 如果创建失败，使用当前目录
    
    # 创建结果日志文件
    log_file = os.path.join(result_dir, "recognition_results.txt")
    with open(log_file, "w", encoding="utf-8") as f:
        f.write("滑动验证码识别结果日志\n")
        f.write("======================\n\n")
        f.write(f"开始时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
        
    def log_result(message):
        """记录结果到日志文件"""
        with open(log_file, "a", encoding="utf-8") as f:
            f.write(message + "\n")
        print(message)
        
    def save_image(img, filename, annotations=None):
        """
        保存图像到结果目录
        annotations: 可选的标注信息 [(x1,y1,x2,y2,color,text), ...]
        """
        full_path = os.path.join(result_dir, filename)
        try:
            if isinstance(img, np.ndarray):
                # 如果有标注信息，绘制在图像上
                if annotations:
                    img_copy = img.copy()
                    for x1, y1, x2, y2, color, text in annotations:
                        cv2.rectangle(img_copy, (x1, y1), (x2, y2), color, 2)
                        cv2.putText(img_copy, text, (x1, y1-10), 
                                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
                    Image.fromarray(img_copy).save(full_path)
                else:
                    Image.fromarray(img).save(full_path)
            else:
                # 如果是Selenium元素的截图
                img.screenshot(full_path)
            print(f"保存图像: {full_path}")
        except Exception as e:
            print(f"保存图像失败 {filename}: {str(e)}")
            
    # 定义公共变量以便所有函数可访问
    scale_factor = 0.5  # 默认初始值
    
    # 修改函数定义，添加log_result和save_image参数
    def find_template_match_improved(full_np, log_func=None, save_func=None, attempt_num=1):
        """
        简化版模板匹配函数 - 聚焦于同一水平线上的滑块缺口检测
        """
        # 使用传入的log_func，如果没有则使用print
        log_func = log_func if log_func else print
        # 使用传入的save_func，如果没有则创建一个什么都不做的函数
        save_func = save_func if save_func else lambda *args, **kwargs: None
        
        log_func("开始精确的边缘检测和模板匹配...")
        
        try:
            h, w = full_np.shape[:2]
            # 记录图像宽高比，用于后续适配不同设备
            aspect_ratio = w / h
            log_func(f"图像宽高比: {aspect_ratio:.4f}")
            
            # Step 1: 使用边缘检测和轮廓分析来动态定位拼图位置
            # 主要关注左侧区域 (图像宽度的25%左右)
            left_region_width = int(w * 0.25)
            left_region = full_np[:, :left_region_width]
            
            # 转换为灰度图并应用边缘检测
            left_gray = cv2.cvtColor(left_region, cv2.COLOR_RGB2GRAY)
            left_edges = cv2.Canny(left_gray, 70, 200)
            save_func(left_edges, f"left_edges_attempt{attempt_num}.png")
            
            # 寻找轮廓
            contours, _ = cv2.findContours(left_edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            log_func(f"在左侧区域找到 {len(contours)} 个轮廓")
            
            # 筛选可能的拼图轮廓
            puzzle_candidates = []
            
            for c in contours:
                area = cv2.contourArea(c)
                # 过滤太小的轮廓
                if area < 500:
                    continue
                
                # 获取轮廓的边界框
                x, y, w_contour, h_contour = cv2.boundingRect(c)
                
                # 计算宽高比 - 拼图通常接近正方形
                aspect_ratio_contour = max(w_contour, h_contour) / min(w_contour, h_contour) if min(w_contour, h_contour) > 0 else 999
                
                # 计算位置得分 - 更偏向于左侧的轮廓
                position_score = 1.0 - (x / left_region_width)
                
                # 计算形状得分 - 更偏向于接近正方形的轮廓
                shape_score = 1.0 / aspect_ratio_contour if aspect_ratio_contour <= 2.0 else 0
                
                # 总得分 - 综合考虑位置和形状
                total_score = position_score * 0.7 + shape_score * 0.3
                
                # 添加到候选列表
                puzzle_candidates.append((c, x, y, w_contour, h_contour, area, total_score))
                log_func(f"候选拼图: 位置=({x},{y}), 大小={w_contour}x{h_contour}, 面积={area}, 得分={total_score:.4f}")
            
            # 选择拼图位置
            if puzzle_candidates:
                # 按得分排序选择最佳候选
                puzzle_candidates.sort(key=lambda item: item[6], reverse=True)
                best_candidate = puzzle_candidates[0]
                _, puzzle_x, puzzle_y, puzzle_width, puzzle_height, area, score = best_candidate
                
                log_func(f"选择最佳拼图候选: 位置=({puzzle_x},{puzzle_y}), 大小={puzzle_width}x{puzzle_height}, 得分={score:.4f}")
                
                # 验证尺寸是否在合理范围内
                expected_width = int(w * 0.135678)
                expected_height = int(h * 0.23717)
                
                if puzzle_width < expected_width * 0.5 or puzzle_width > expected_width * 1.5:
                    log_func(f"拼图宽度 {puzzle_width} 不合理，调整为默认宽度 {expected_width}")
                    puzzle_width = expected_width
                
                if puzzle_height < expected_height * 0.5 or puzzle_height > expected_height * 1.5:
                    log_func(f"拼图高度 {puzzle_height} 不合理，调整为默认高度 {expected_height}")
                    puzzle_height = expected_height
            else:
                # 如果未找到合适的候选拼图，使用基于边缘分布的启发式方法
                log_func("未找到合适的拼图候选，使用边缘分布分析")
                
                # 创建垂直区域掩码，分上中下三个区域
                top_region = np.zeros_like(left_gray)
                middle_region = np.zeros_like(left_gray)
                bottom_region = np.zeros_like(left_gray)
                
                top_region[:int(h*0.33), :] = 255
                middle_region[int(h*0.33):int(h*0.66), :] = 255
                bottom_region[int(h*0.66):, :] = 255
                
                # 计算每个区域的边缘点数
                top_edges = cv2.bitwise_and(left_edges, left_edges, mask=top_region)
                middle_edges = cv2.bitwise_and(left_edges, left_edges, mask=middle_region)
                bottom_edges = cv2.bitwise_and(left_edges, left_edges, mask=bottom_region)
                
                top_count = np.count_nonzero(top_edges)
                middle_count = np.count_nonzero(middle_edges)
                bottom_count = np.count_nonzero(bottom_edges)
                
                log_func(f"边缘分布: 上={top_count}, 中={middle_count}, 下={bottom_count}")
                
                # 基于边缘分布估计拼图垂直位置
                if bottom_count > middle_count and bottom_count > top_count:
                    puzzle_y = int(h * 0.75)  # 拼图可能在下部
                    log_func("拼图可能在下部区域")
                elif middle_count > top_count:
                    puzzle_y = int(h * 0.5)  # 拼图可能在中部
                    log_func("拼图可能在中部区域")
                else:
                    puzzle_y = int(h * 0.25)  # 拼图可能在上部
                    log_func("拼图可能在上部区域")
                
                # 水平位置使用较小值，因为拼图通常在左边缘
                puzzle_x = int(w * 0.02)  # 左边缘附近
                puzzle_width = int(w * 0.135678)  # 使用预期宽度
                puzzle_height = int(h * 0.23717)  # 使用预期高度
            
            log_func(f"最终拼图位置: x={puzzle_x}, y={puzzle_y}, width={puzzle_width}, height={puzzle_height}")
            
            # 确保坐标不超出图像边界
            puzzle_x = max(0, min(puzzle_x, w - puzzle_width))
            puzzle_y = max(0, min(puzzle_y, h - puzzle_height))
            
            # Step 2: 提取拼图模板
            puzzle_template = full_np[puzzle_y:puzzle_y+puzzle_height, puzzle_x:puzzle_x+puzzle_width]
            puzzle_gray = cv2.cvtColor(puzzle_template, cv2.COLOR_RGB2GRAY)
            
            # 对模板进行边缘检测
            puzzle_edges = cv2.Canny(puzzle_gray, 70, 200)
            save_func(puzzle_edges, f"puzzle_template_attempt{attempt_num}.png")
            save_func(puzzle_template, f"puzzle_template_rgb_attempt{attempt_num}.png")
            
            # Step 3: 在右侧80%区域搜索缺口，设置合理的搜索范围
            search_start_x = int(w * 0.2)  # 从图像20%宽度开始搜索
            search_region = full_np[:, search_start_x:]
            search_gray = cv2.cvtColor(search_region, cv2.COLOR_RGB2GRAY)
            
            # 设定搜索区域 - 在与拼图相同高度的水平带上搜索
            search_y_min = max(0, puzzle_y - puzzle_height//2)
            search_y_max = min(h, puzzle_y + puzzle_height*3//2)
            
            # 提取右侧区域中的搜索带
            search_band = search_gray[search_y_min:search_y_max, :]
            
            # 对搜索带应用边缘检测
            search_edges = cv2.Canny(search_band, 70, 200)
            save_func(search_edges, f"search_band_attempt{attempt_num}.png")
            
            # 使用模板匹配在搜索带中寻找与拼图相似的缺口
            res = cv2.matchTemplate(search_edges, puzzle_edges, cv2.TM_CCOEFF_NORMED)
            _, max_val, _, max_loc = cv2.minMaxLoc(res)
            
            log_func(f"模板匹配结果: 最佳匹配度={max_val:.4f}, 位置={max_loc}")
            
            # 如果匹配度足够高，认为找到了有效缺口
            if max_val > 0.15:  # 降低阈值，因为边缘匹配通常得分较低
                # 计算缺口在原图中的坐标
                gap_x = max_loc[0] + search_start_x
                gap_y = max_loc[1] + search_y_min
                
                # 计算拼图中心和缺口中心
                puzzle_center_x = puzzle_x + puzzle_width // 2
                gap_center_x = gap_x + puzzle_width // 2
                
                # 计算所需的偏移量
                required_offset = gap_center_x - puzzle_center_x
                
                # 验证偏移量是否合理
                if required_offset < w * 0.1:  # 偏移量过小（小于图像宽度的10%）
                    log_func(f"警告: 计算的偏移量过小 ({required_offset}px)，可能不正确")
                    required_offset = w * 0.5  # 默认值为图像宽度的50%
                
                # 可视化结果
                result_img = full_np.copy()
                
                # 标记拼图位置
                cv2.rectangle(result_img, 
                             (puzzle_x, puzzle_y), 
                             (puzzle_x + puzzle_width, puzzle_y + puzzle_height), 
                             (0, 255, 0), 2)
                
                # 标记缺口位置
                cv2.rectangle(result_img, 
                             (gap_x, gap_y), 
                             (gap_x + puzzle_width, gap_y + puzzle_height), 
                             (0, 0, 255), 2)
                
                # 画出拼图中心线和缺口中心线
                cv2.line(result_img, (puzzle_center_x, 0), (puzzle_center_x, h), (255, 0, 0), 1)
                cv2.line(result_img, (gap_center_x, 0), (gap_center_x, h), (0, 0, 255), 1)
                
                # 画出需要移动的箭头
                cv2.arrowedLine(result_img, (puzzle_center_x, 50), (gap_center_x, 50), (255, 0, 255), 2)
                
                # 添加匹配信息文本
                cv2.putText(result_img, 
                           f"Match: {max_val:.2f}, Offset: {required_offset}px", 
                            (10, 30), 
                            cv2.FONT_HERSHEY_SIMPLEX, 
                            0.7, 
                            (255, 255, 255), 
                            2)
                
                # 保存结果图像
                save_func(result_img, f"matched_gap_with_offset_attempt{attempt_num}.png")
                
                log_func(f"找到缺口: 位置=({gap_x},{gap_y}), 置信度={max_val:.4f}")
                log_func(f"拼图中心={puzzle_center_x}, 缺口中心={gap_center_x}, 需要的偏移量={required_offset}")
                
                return required_offset, gap_center_x
            else:
                log_func(f"模板匹配未找到高置信度缺口 (最佳匹配度={max_val:.4f})")
                
                # 尝试使用备用方法 - 整体二值化检测
                log_func("尝试使用整体二值化检测...")
                
                # 对整个右侧区域应用二值化
                _, thresh = cv2.threshold(search_gray, 220, 255, cv2.THRESH_BINARY)
                kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
                thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
                thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
                
                # 保存二值化结果
                save_func(thresh, f"threshold_attempt{attempt_num}.png")
                
                # 在适当高度范围内寻找轮廓
                thresh_band = thresh[search_y_min:search_y_max, :]
                contours, _ = cv2.findContours(thresh_band, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
                
                # 筛选合适的轮廓
                gap_candidates = []
                for c in contours:
                    area = cv2.contourArea(c)
                    if area < 100:  # 过滤太小的轮廓
                        continue
                        
                    x, y, w_c, h_c = cv2.boundingRect(c)
                    
                    # 计算轮廓在原图中的位置
                    abs_x = x + search_start_x
                    abs_y = y + search_y_min
                    
                    # 计算与拼图的大小相似度
                    area_similarity = min(w_c*h_c, puzzle_width*puzzle_height) / max(w_c*h_c, puzzle_width*puzzle_height)
                    
                    # 计算与拼图宽高比的相似度
                    aspect_similarity = 1.0 - min(1.0, abs((w_c/h_c) - (puzzle_width/puzzle_height)))
                    
                    # 总分
                    score = area_similarity * 0.6 + aspect_similarity * 0.4
                    
                    gap_candidates.append((abs_x, abs_y, w_c, h_c, score))
                
                if gap_candidates:
                    # 按得分排序
                    gap_candidates.sort(key=lambda x: x[4], reverse=True)
                    gap_x, gap_y, gap_w, gap_h, score = gap_candidates[0]
                    
                    # 计算中心和偏移量
                    puzzle_center_x = puzzle_x + puzzle_width // 2
                    gap_center_x = gap_x + gap_w // 2
                    required_offset = gap_center_x - puzzle_center_x
                    
                    # 验证偏移量是否合理
                    if required_offset < w * 0.1:  # 偏移量过小（小于图像宽度的10%）
                        log_func(f"警告: 计算的偏移量过小 ({required_offset}px)，可能不正确")
                        required_offset = w * 0.5  # 默认值为图像宽度的50%
                    
                    # 可视化结果
                    result_img = full_np.copy()
                    
                    # 标记拼图和缺口
                    cv2.rectangle(result_img, (puzzle_x, puzzle_y), (puzzle_x + puzzle_width, puzzle_y + puzzle_height), (0, 255, 0), 2)
                    cv2.rectangle(result_img, (gap_x, gap_y), (gap_x + gap_w, gap_y + gap_h), (0, 0, 255), 2)
                    
                    # 中心线和箭头
                    cv2.line(result_img, (puzzle_center_x, 0), (puzzle_center_x, h), (255, 0, 0), 1)
                    cv2.line(result_img, (gap_center_x, 0), (gap_center_x, h), (0, 0, 255), 1)
                    cv2.arrowedLine(result_img, (puzzle_center_x, 50), (gap_center_x, 50), (255, 0, 255), 2)
                    
                    # 文本信息
                    cv2.putText(result_img, 
                               f"Threshold: Score={score:.2f}, Offset={required_offset}px", 
                                (10, 30), 
                                cv2.FONT_HERSHEY_SIMPLEX, 
                                0.7, 
                                (255, 255, 255), 
                                2)
                    
                    save_func(result_img, f"threshold_match_attempt{attempt_num}.png")
                    
                    log_func(f"二值化方法找到缺口: 位置=({gap_x},{gap_y}), 得分={score:.4f}")
                    log_func(f"拼图中心={puzzle_center_x}, 缺口中心={gap_center_x}, 偏移量={required_offset}")
                    
                    return required_offset, gap_center_x
            
            # 如果所有方法都失败，使用默认估计值
            log_func("所有检测方法都失败，使用默认估计值")
            puzzle_center_x = puzzle_x + puzzle_width // 2
            default_gap_x = int(w * 0.6)  # 缺口在60%宽度处
            default_offset = default_gap_x - puzzle_center_x
            
            # 可视化结果
            result_img = full_np.copy()
            cv2.rectangle(result_img, (puzzle_x, puzzle_y), (puzzle_x + puzzle_width, puzzle_y + puzzle_height), (0, 255, 0), 2)
            
            # 绘制默认缺口位置
            cv2.rectangle(
                result_img,
                (default_gap_x - puzzle_width//2, puzzle_y),
                (default_gap_x + puzzle_width//2, puzzle_y + puzzle_height),
                (0, 0, 255),
                2
            )
            
            # 中心线和箭头
            cv2.line(result_img, (puzzle_center_x, 0), (puzzle_center_x, h), (255, 0, 0), 1)
            cv2.line(result_img, (default_gap_x, 0), (default_gap_x, h), (0, 0, 255), 1)
            cv2.arrowedLine(result_img, (puzzle_center_x, 50), (default_gap_x, 50), (255, 0, 255), 2)
            
            # 信息文本
            cv2.putText(
                result_img,
                f"默认估计偏移量: {default_offset}px",
                (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.7,
                (255, 255, 255),
                2
            )
            
            save_func(result_img, f"default_estimate_attempt{attempt_num}.png")
            
            return default_offset, default_gap_x
                
        except Exception as e:
            log_func(f"滑块缺口检测出错: {str(e)}")
            traceback.print_exc()  # 打印完整的错误堆栈
            # 出错时返回一个默认偏移量
            h, w = full_np.shape[:2]
            default_offset = w * 0.4  # 默认移动到40%宽度处
            default_gap_x = int(w * 0.6)
            log_func(f"错误后使用默认偏移量: {default_offset}px")
            return default_offset, default_gap_x

    def capture_and_process(attempt_num=1):
        """Capture and process the captcha - returns the calculated offset"""
        nonlocal scale_factor  # 使用外部变量
        
        # 1) grab captcha container
        captcha_el = WebDriverWait(driver, 20).until(
            EC.visibility_of_element_located((By.CLASS_NAME, "verify-img-out"))
        )
        
        # 2) Adaptive waiting for captcha to render
        full_np = wait_for_captcha_rendering(driver, captcha_el, max_wait=20)
        
        # 记录图像尺寸
        h1, w1 = full_np.shape[:2]
        log_result(f"验证码图像尺寸: {w1}x{h1}, 宽高比: {w1/h1:.4f}")
        
        # 传递log_result和save_image函数，使用改进的模板匹配方法
        gap_offset, gap_center_x = find_template_match_improved(full_np, log_result, save_image, attempt_num=attempt_num)
        
        # 如果检测失败，使用默认值
        if gap_offset is None:
            log_result("所有检测方法都失败，使用随机位置")
            gap_offset = w1 * 0.4  # 生成在40%范围的偏移
            gap_center_x = int(w1 * 0.6)  # 缺口位置在60%处
            log_result(f"使用默认偏移量: {gap_offset:.1f}")
        
        # Calculate scaling factor for browser coordinates
        captcha_width = captcha_el.size['width']
        image_width = full_np.shape[1]
        scale_factor = captcha_width / image_width  # 更新全局变量
        
        # 计算最终偏移量 - 移动距离的调整系数根据图像宽高比动态调整
        # 宽高比越大，调整越小；宽高比越小，调整越大
        aspect_ratio = w1 / h1
        
        # 创建自适应的调整系数
        if aspect_ratio > 2.0:  # 超宽屏幕
            adjust_factor = 0.08  # 较小的调整
        elif aspect_ratio > 1.5:  # 标准宽屏
            adjust_factor = 0.10  # 中等调整
        else:  # 接近正方形的屏幕
            adjust_factor = 0.13  # 较大的调整
        
        log_result(f"图像宽高比: {aspect_ratio:.4f}, 使用调整系数: {adjust_factor:.4f}")
        
        # 根据移动距离动态计算额外偏移
        additional_offset = gap_offset * adjust_factor  # 移动距离的调整
        
        # 最终偏移量
        final_offset = gap_offset
        
        log_result(f"基础偏移量: {gap_offset}, 额外偏移量: {additional_offset:.1f}")
        log_result(f"最终偏移量: {final_offset}")
        
        # 保存带标记的结果图像
        result_img = full_np.copy()
        
        # 计算拼图位置
        puzzle_x = int(w1 * 0.076633)  # 拼图左侧位置
        puzzle_width = int(w1 * 0.135678)  # 拼图宽度
        
        # 计算拼图中心
        puzzle_center_x = puzzle_x + puzzle_width // 2
        
        # 标记拼图和缺口位置
        # 调整垂直位置基于图像宽高比
        if aspect_ratio > 2.0:
            puzzle_y = int(h1 * 0.65)
        elif aspect_ratio > 1.5:
            puzzle_y = int(h1 * 0.6)
        else:
            puzzle_y = int(h1 * 0.55)
        
        puzzle_height = int(h1 * 0.23717)
        
        # 标记拼图位置
        cv2.rectangle(result_img, 
                     (int(puzzle_x), int(puzzle_y)), 
                     (int(puzzle_x + puzzle_width), int(puzzle_y + puzzle_height)), 
                     (0, 255, 0), 2)
        
        # 标记拼图中心
        cv2.circle(result_img, (int(puzzle_center_x), int(puzzle_y + puzzle_height//2)), 5, (0, 255, 0), -1)
        
        # 标记缺口位置
        gap_rect_x = int(gap_center_x - puzzle_width // 2)
        cv2.rectangle(result_img, 
                     (gap_rect_x, int(puzzle_y)), 
                     (gap_rect_x + int(puzzle_width), int(puzzle_y + puzzle_height)), 
                     (0, 0, 255), 2)
        
        # 标记缺口中心
        cv2.circle(result_img, (int(gap_center_x), int(puzzle_y + puzzle_height//2)), 5, (0, 0, 255), -1)
        
        # 画出拼图中心线
        cv2.line(result_img, (int(puzzle_center_x), 0), (int(puzzle_center_x), h1), (255, 0, 0), 1)
        
        # 画出缺口中心线
        cv2.line(result_img, (int(gap_center_x), 0), (int(gap_center_x), h1), (0, 0, 255), 1)
        
        # 画出移动箭头
        cv2.arrowedLine(result_img, (int(puzzle_center_x), 30), (int(gap_center_x), 30), (0, 0, 255), 2)
        
        # 添加说明文本
        cv2.putText(result_img, 
                   f"偏移量: {final_offset:.1f}px (调整系数: {adjust_factor:.3f})", 
                    (10, h1-20), 
                    cv2.FONT_HERSHEY_SIMPLEX, 
                    0.6, 
                    (0, 0, 255), 
                    2)
        
        # 保存最终图像
        save_image(result_img, f"captcha_target_marked_attempt{attempt_num}.png")
        
        # 计算最终移动距离 - 考虑目标缺口和调整
        # 1. 基础偏移量基于缺口与拼图的距离
        # 2. 使用加权平均优化最终位置
        # 这里的0.13是基于手动测试得出的最佳值，对应于图像的宽高比
        move_offset = gap_center_x * (1 - adjust_factor) + (gap_center_x + final_offset) * adjust_factor
        
        # 转换为浏览器坐标系
        browser_offset = move_offset * scale_factor
        
        log_result(f"图像宽度: {image_width}, 验证码容器宽度: {captcha_width}")
        log_result(f"缩放因子: {scale_factor}")
        log_result(f"缺口中心位置: {gap_center_x}, 最终移动位置: {move_offset}, 浏览器坐标系偏移量: {browser_offset}")
        
        # 确保偏移量在合理范围内
        min_offset = 0.1 * captcha_width  # 最小偏移量为容器宽度的10%
        max_offset = captcha_width  # 最大偏移量为容器宽度的95%
        
        # 防止偏移量超出合理范围
        if browser_offset < min_offset and browser_offset != 0:  # 允许0作为有效值进行测试
            log_result(f"偏移量过小 ({browser_offset}), 强制使用最小偏移量 {min_offset}")
            browser_offset = min_offset
        elif browser_offset > max_offset:
            log_result(f"偏移量过大 ({browser_offset}), 强制使用最大偏移量 {max_offset}")
            browser_offset = max_offset
        
        return browser_offset, captcha_el, full_np
    
    # First attempt - capture, process, and slide
    log_result("\n===== 第一次识别尝试 =====")
    try:
        offset, captcha_el, full_np = capture_and_process(attempt_num=1)
    
        def do_slide(slide_offset, attempt_num=1, captcha_element=None):
            """Perform the sliding action with enhanced human-like movement"""
            log_result(f"滑动到偏移量: {slide_offset} (尝试 #{attempt_num})")
            
            try:
                slider = WebDriverWait(driver, 10).until(
                    EC.element_to_be_clickable((By.CLASS_NAME, "verify-move-block"))
                )
                
                # 获取滑块位置，用于后续计算
                slider_rect = slider.rect
                log_result(f"滑块位置: x={slider_rect['x']}, y={slider_rect['y']}, w={slider_rect['width']}, h={slider_rect['height']}")
                
                # 关键修复：考虑滑块元素在浏览器中的实际位置
                starting_x = slider_rect['x']
                
                # 直接使用缺口中心位置作为偏移量
                # 这个偏移量是两条线之间的距离（滑块中心线和缺口中心线）
                total_distance = slide_offset
                target_x = starting_x + total_distance
                
                log_result(f"滑块起始位置: {starting_x}, 目标位置(缺口中心): {target_x}, 总距离: {total_distance}")
                
                # Three-phase human-like movement
                action = ActionChains(driver)
                action.click_and_hold(slider)
                
                # Add a small initial pause after click (like a human thinking)
                action.pause(random.uniform(0.1, 0.3))
                
                # Phase 1: Initial hesitant/slow movement (first 15% of distance)
                phase1_distance = math.ceil(total_distance * 0.15)
                current = 0
                log_result(f"阶段1: 初始慢速移动 - {phase1_distance:.1f}px")
                
                while current < phase1_distance:
                    # Small movements with vertical jitter
                    move_x = random.uniform(1, 3)
                    move_y = random.uniform(-1, 1)  # Small vertical movements
                    action.move_by_offset(move_x, move_y)
                    current += move_x
                    
                    # Random pauses to simulate hesitation
                    action.pause(random.uniform(0.05, 0.15))
                
                # Phase 2: Main movement - faster with momentum (next 70% of distance)
                phase2_distance = math.ceil(total_distance * 0.7)
                phase2_end = current + phase2_distance
                log_result(f"阶段2: 主要快速移动 - {phase2_distance:.1f}px")
                
                while current < phase2_end:
                    # Larger, faster movements with slight vertical jitter
                    # 根据剩余距离动态调整移动速度
                    remaining = phase2_end - current
                    if remaining > 50:
                        move_x = random.uniform(5, 10)
                    elif remaining > 20:
                        move_x = random.uniform(3, 6)
                    else:
                        move_x = random.uniform(1, 3)
                    
                    move_y = random.uniform(-0.5, 0.5)
                    
                    # Don't overshoot phase 2
                    if current + move_x > phase2_end:
                        move_x = phase2_end - current
                        
                    action.move_by_offset(move_x, move_y)
                    current += move_x
                    
                    # Shorter pauses during main movement
                    action.pause(random.uniform(0.01, 0.03))
                
                # Phase 3: Final precision adjustment (remaining 15% of distance)
                remaining = total_distance - current
                log_result(f"阶段3: 最终精确移动 - {remaining:.1f}px")
                
                while remaining > 0:
                    # Very small, precise movements
                    move_x = min(random.uniform(0.5, 2), remaining)
                    move_y = random.uniform(-0.3, 0.3)
                    
                    action.move_by_offset(move_x, move_y)
                    remaining -= move_x
                    
                    # Longer pauses during precision phase
                    action.pause(random.uniform(0.1, 0.2))
                
                # Final pause before release (like a human verifying position)
                action.pause(random.uniform(0.2, 0.5))
                # add small horizontal jitter for realism
                action.move_by_offset(random.uniform(-3,3), random.uniform(-1,1))
                action.release()
                
                # Sometimes humans move the mouse away after sliding
                if random.random() > 0.5:
                    action.move_by_offset(random.uniform(5, 20), random.uniform(-10, 10))
                    
                action.perform()
                
                log_result("滑动完成")
                
                # 滑动完成后截图
                time.sleep(0.5)  # 等待滑动动画完成
                try:
                    if captcha_element is not None:
                        # 截取整个验证码区域的屏幕截图
                        captcha_png = captcha_element.screenshot_as_png
                        captcha_img = Image.open(io.BytesIO(captcha_png))
                        # save_image(np.array(captcha_img), f"captcha_finished_attempt{attempt_num}.png")
                        # log_result(f"已保存滑动完成后的截图: captcha_finished_attempt{attempt_num}.png")
                    else:
                        # 如果没有传入captcha_element，尝试截取整个页面
                        driver.save_screenshot(f"captcha_finished_attempt{attempt_num}.png")
                        log_result(f"已保存页面截图: captcha_finished_attempt{attempt_num}.png")
                except Exception as e:
                    log_result(f"无法保存滑动后的截图: {str(e)}")
                
                return True
            except Exception as e:
                log_result(f"滑动操作失败: {str(e)}")
                return False
        
        # First attempt
        slide_success = do_slide(offset, attempt_num=1, captcha_element=captcha_el)
        if not slide_success:
            log_result("第一次滑动失败，直接进入第二次尝试")
            raise Exception("滑动操作失败")
            
        time.sleep(2)  # Wait for animation to complete
        
        time.sleep(1)  # Short pause before checking result
        
        # More comprehensive verification failure detection
        # Check if verification failed and retry with complete recapture and reanalysis
        failure_markers = ["验证失败", "verification failed", "try again", "重试", "失败"]
        verification_failed = any(marker in driver.page_source.lower() for marker in failure_markers)
        
    except Exception as e:
        log_result(f"第一次尝试过程中出错: {str(e)}")
        verification_failed = True
    
    try:
        if verification_failed:
            log_result("\n===== 第一次尝试失败，等待5秒后重试 =====")
            time.sleep(5)
            
            # Complete retry - recapture and reanalyze
            new_offset, captcha_el, full_np = capture_and_process(attempt_num=2)
            
            # 直接使用原始计算的偏移量
            slide_success = do_slide(new_offset, attempt_num=2, captcha_element=captcha_el)
            if not slide_success:
                log_result("第二次滑动失败，直接进入第三次尝试")
                raise Exception("第二次滑动操作失败")
                
            time.sleep(2)  # Wait for animation to complete
            
            # If still failed after second attempt, try one more time
            verification_still_failed = any(marker in driver.page_source.lower() for marker in failure_markers)
            
        else:
            verification_still_failed = False
    except Exception as e:
        log_result(f"第二次尝试过程中出错: {str(e)}")
        verification_still_failed = True
    
    try:
        if verification_failed and verification_still_failed:
            log_result("\n===== 第二次尝试也失败，进行最后一次尝试 =====")
            time.sleep(5)
            
            # Third attempt - use original calculation again
            final_offset, captcha_el, full_np = capture_and_process(attempt_num=3)
            
            # 直接使用原始计算的偏移量
            do_slide(final_offset, attempt_num=3, captcha_element=captcha_el)
            time.sleep(2)
            
        elif not verification_failed:
            log_result("第一次尝试验证成功!")
        else:
            log_result("第二次尝试验证成功!")
    except Exception as e:
        log_result(f"第三次尝试过程中出错: {str(e)}")
    
    # 记录总结信息
    with open(log_file, "a", encoding="utf-8") as f:
        f.write(f"\n\n结束时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
        if not verification_failed:
            f.write("最终结果: 第一次尝试成功\n")
        elif verification_failed and not verification_still_failed:
            f.write("最终结果: 第二次尝试成功\n")
        else:
            f.write("最终结果: 所有尝试均失败\n")


class Task51(BaseDownloaderFirefox):
    def login(self):
        """Login to the website with enhanced captcha handling"""
        # Navigate to login page
        self.driver.get("http://gys.yndzyf.com/")
        print("Navigating to login page")
        
        try:
            # Fill in the credentials
            print("Filling in login credentials...")
            
            # Fill in the first input field (tlxkh)
            first_field = WebDriverWait(self.driver, 10).until(
                EC.visibility_of_element_located((By.ID, "tlxkh"))
            )
            first_field.clear()
            first_field.send_keys("4505")
            
            # Fill in the second input field (tusername)
            second_field = WebDriverWait(self.driver, 10).until(
                EC.visibility_of_element_located((By.ID, "tusername"))
            )
            second_field.clear()
            second_field.send_keys("abc")
            
            # Fill in the third input field (tpass)
            third_field = WebDriverWait(self.driver, 10).until(
                EC.visibility_of_element_located((By.ID, "tpass"))
            )
            third_field.clear()
            third_field.send_keys("abc")
            
            # Click the login verification button
            print("Clicking login verification button...")
            login_verification_button = WebDriverWait(self.driver, 10).until(
                EC.element_to_be_clickable((By.ID, "yzm"))
            )
            login_verification_button.click()
            
            # Wait a moment for the captcha to appear
            time.sleep(2)
            
            # Handle the slider verification using the enhanced function
            print("Handling slider captcha verification...")
            solve_slider_captcha(self.driver)
            
            # Wait for successful login
            print("Waiting for successful login...")
            WebDriverWait(self.driver, 15).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, ".user-info"))
            )
            
            print("Login successful!")
            return True
            
        except TimeoutException as e:
            print(f"Login timeout: {str(e)}")
            # Save screenshot for debugging
            self.driver.save_screenshot("login_timeout.png")
            return False
        except Exception as e:
            print(f"Login error: {str(e)}")
            # Save screenshot for debugging
            self.driver.save_screenshot("login_error.png")
            return False

    def retrieve_data(self):
        """Retrieve data after successful login"""
        # Implementation to be added
        pass

    def download_file(self):
        """Download required files after successful login"""
        # Implementation to be added
        pass


# Example usage - when running this script directly
if __name__ == "__main__":
    print("Starting Task51 with enhanced biological pattern captcha handling...")
    task = Task51()
    task.prepare_environment()
    
    success = task.login()
    
    if success:
        print("Login successful - now you can proceed with data retrieval")
    else:
        print("Login failed - check the error logs and screenshots")
    
    task.driver.quit()
